﻿using Apewer.Network;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Apewer.Web
{

    /// <summary>API 行为。</summary>
    public sealed class ApiAction : IToJson
    {

        #region fields

        Type _type = null;
        MethodInfo _method = null;

        string _path = null;
        HttpMethod[] _methods = null;
        ApiParameter[] _parameters = null;

        #endregion

        #region propeties

        /// <summary>控制器的反射类型。</summary>
        public Type Type { get => _type; }

        /// <summary>API 行为的反射方法。</summary>
        public MethodInfo MethodInfo { get => _method; }

        /// <summary>URL 路径。</summary>
        public string Path { get => _path; }

        /// <summary>HTTP 方法。</summary>
        public HttpMethod[] Methods
        {
            get
            {
                var result = new HttpMethod[_methods.Length];
                if (_methods.Length > 0) _methods.CopyTo(result, 0);
                return result;
            }
        }

        /// <summary>参数。</summary>
        public ApiParameter[] Parameters
        {
            get
            {
                var result = new ApiParameter[_parameters.Length];
                if (_parameters.Length > 0) _parameters.CopyTo(result, 0);
                return result;
            }
        }

        /// <summary>生成 JSON 实例。</summary>
        public Json ToJson() => ToJson(null);

        /// <summary>生成 JSON 实例。</summary>
        public Json ToJson(ApiActionJsonFormat format)
        {
            if (format == null) format = ApiActionJsonFormat.Default ?? new ApiActionJsonFormat();

            var methods = new Json();
            foreach (var method in _methods)
            {
                methods.AddItem(method.ToString().Lower());
            }

            var json = new Json();
            json.SetProperty("path", _path);
            json.SetProperty("methods", methods);

            if (format.WithReflection)
            {
                var reflection = new Json();
                reflection.SetProperty("type", _type.FullName);
                reflection.SetProperty("method", _method.Name);
                json.SetProperty("reflection", reflection);
            }

            if (format.WithParameters && _parameters.Length > 0)
            {
                var parameters = Json.NewArray();
                foreach (var parameter in _parameters)
                {
                    parameters.AddItem(parameter.ToJson(format.WithReflection));
                }
                json.SetProperty("parameters", parameters);
            }

            return json;
        }

        /// <summary>生成字符串。</summary>
        public override string ToString() => _path;

        #endregion

        #region parse

        const string Separator = "/";

        /// <summary>创建 API 行为描述实例。</summary>
        /// <param name="type">控制器的反射类型。</param>
        /// <param name="method">API 行为的反射方法。</param>
        /// <param name="path">URL 路径。</param>
        /// <param name="methods">HTTP 方法。</param>
        /// <param name="parameters">参数。</param>
        /// <exception cref="ArgumentNullException" />
        /// <exception cref="ArgumentException" />
        ApiAction(Type type, MethodInfo method, string path, HttpMethod[] methods, ApiParameter[] parameters)
        {
            if (type == null) throw new ArgumentNullException(nameof(type));
            if (method == null) throw new ArgumentNullException(nameof(method));
            if (method.IsAbstract) throw new ArgumentException($"参数 {nameof(method)} 是抽象的。");
            if (path.IsEmpty()) throw new ArgumentNullException(nameof(path));
            if (methods.IsEmpty()) throw new ArgumentNullException(nameof(methods));

            var split = path.Split('/');
            var segs = split.Trim();
            path = segs.Length < 1 ? Separator : (Separator + string.Join(Separator, segs));

            _type = type;
            _method = method;
            _path = path;
            _methods = methods;
            _parameters = parameters;
        }

        /// <summary>解析控制器类型，获取 API 活动。</summary>
        /// <exception cref="ArgumentNullException" />
        public static ApiAction[] Parse(Type type)
        {
            if (type == null) throw new ArgumentNullException(nameof(type));

            if (type.FullName == "Front.Debug.Controller")
            {
            }

            // 检查类型的属性。
            if (!type.IsClass) return new ApiAction[0];
            if (type.IsAbstract) return new ApiAction[0];
            if (type.IsGenericType) return new ApiAction[0];
            if (type.GetGenericArguments().NotEmpty()) return new ApiAction[0];
            if (!RuntimeUtility.CanNew(type)) return new ApiAction[0];

            // 判断基类。
            if (!typeof(ApiController).IsAssignableFrom(type)) return new ApiAction[0];

            // 读取 URL 前缀。
            var prefixAttribute = RuntimeUtility.GetAttribute<RoutePrefixAttribute>(type, false);
            var prefixPath = (prefixAttribute == null || prefixAttribute.Path.IsEmpty()) ? null : prefixAttribute.Path.Split('/').Trim();
            // if (prefixPath.IsEmpty())
            // {
            //     // 读取 API 特性。
            //     var api = RuntimeUtility.GetAttribute<ApiAttribute>(type);
            //     if (api != null)
            //     {
            //         var apiName = api.Name.ToTrim();
            //         if (apiName.Lower().EndsWith("controller")) apiName = apiName.Substring(0, apiName.Length - 10);
            //         if (apiName.NotEmpty()) prefixPath = new string[] { apiName };
            //     }
            // }

            // 读取方法。
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            var actions = new List<ApiAction>(methods.Length);
            foreach (var method in methods)
            {
                // 不支持构造函数和泛型。
                if (method.IsConstructor) continue;
                if (method.IsGenericMethod) continue;

                // 抽象类无法创建实例。
                if (method.IsAbstract) continue;
                if (!method.DeclaringType.Equals(type)) continue;

                // 必须有 Route 特性
                var route = RuntimeUtility.GetAttribute<RouteAttribute>(method);
                if (route == null) continue;

                // 确定路径
                var path = route.Path;
                if (path.IsEmpty())
                {
                    if (prefixPath.IsEmpty()) continue;
                    path = method.Name;
                }
                path = ConcatPath(prefixPath, path.Split('/').Trim());

                // 必须有 HTTP 方法
                var httpMethods = new List<HttpMethod>(9);
                if (RuntimeUtility.Contains<HttpConnectAttribute>(method)) httpMethods.Add(HttpMethod.CONNECT);
                if (RuntimeUtility.Contains<HttpDeleteAttribute>(method)) httpMethods.Add(HttpMethod.DELETE);
                if (RuntimeUtility.Contains<HttpGetAttribute>(method)) httpMethods.Add(HttpMethod.GET);
                if (RuntimeUtility.Contains<HttpHeadAttribute>(method)) httpMethods.Add(HttpMethod.HEAD);
                if (RuntimeUtility.Contains<HttpOptionsAttribute>(method)) httpMethods.Add(HttpMethod.OPTIONS);
                if (RuntimeUtility.Contains<HttpPatchAttribute>(method)) httpMethods.Add(HttpMethod.PATCH);
                if (RuntimeUtility.Contains<HttpPostAttribute>(method)) httpMethods.Add(HttpMethod.POST);
                if (RuntimeUtility.Contains<HttpPutAttribute>(method)) httpMethods.Add(HttpMethod.PUT);
                if (RuntimeUtility.Contains<HttpTraceAttribute>(method)) httpMethods.Add(HttpMethod.TRACE);
                if (httpMethods.Count < 1) continue;

                // 参数
                var parameters = new List<ApiParameter>();
                foreach (var pi in method.GetParameters())
                {
                    var parameter = ApiParameter.Parse(pi);
                    if (parameter == null) continue;
                    parameters.Add(parameter);
                }

                var action = new ApiAction(type, method, path, httpMethods.ToArray(), parameters.ToArray());
                actions.Add(action);
            }

            return actions.ToArray();
        }

        static string ConcatPath(string[] prefix, string[] path)
        {
            var list = new List<string>();
            if (prefix != null) list.AddRange(prefix);
            if (path != null) list.AddRange(path);

            var segs = list.Trim();
            if (segs.Length < 1) return Separator;

            var result = Separator + string.Join(Separator, segs);
            return result;
        }

        #endregion

    }

}
